function Job(name,percTotal,end){
	var _name = name;
	var _actions = [];
	var _taskManager;
	var _data = [];
	var _isDone = false;
	var _toGo=0;
	var _maxActions=0;

	this.addAction = function(a){_toGo+=1;_actions.unshift(a);}
	this.getNext = function(){return _actions.pop();}

	var that = this;

	var success = function(data,split){
		if(split){
			if(control.debug) console.log("SUCCESS SPLIT");
			_toGo-=1;
		}else{
			_data.push(data);
			_toGo-=1;
			if(_toGo==0) _isDone = true;			
		}

	}

	var error = function(data){
		console.log("ERROR",data);
		_toGo-=1;
		if(_toGo==0) _isDone = true;
	}

	var checkJob = function(){
		if(control.debug) console.log("CHECK");

		if(_isDone){
			clearInterval(_taskManager);
			end(_data);
			if(control.debug) console.log(_name + " ENDED");
			return;
		}

		while(_actions.length>0){
			that.getNext().send(success,error,that.addAction);
		}
	}	

	this.run = function(){
		_maxActions = _actions.length;
		_taskManager = window.setInterval(checkJob,500);
	}

	this.getPerc = function(){
		var x = (_maxActions - _toGo);
		if(x<0) return percTotal*0.5;
		if(_maxActions==0) return 0;
		return percTotal*x/_maxActions;
	}

	this.running = function(){
		return !_isDone;
	}
}

function FacebookAction(batch,weight,success,error){
	if(batch.length==0) this.batch = null;
	else this.batch = {"batch":JSON.stringify(batch)};

	this.weight = weight;
	this.success = success;
	this.error = error;
}

function FacebookController(){
	var _requests = 0;
	var _init = false;
	var _begin;
	var _interval = 450;
	var _queue = [];
	var _stopped=false;

	this.queueSize = function(){return _queue.length;}

	var runNext = function(action){

		var _now = new Date().getTime()/1000;
		var _diff = _now - _begin;
		var _rate = _requests/_diff;

		//changeInterval(100);

		if(_queue.length>0){
			var action = _queue.shift();					
			_requests+=action.weight;

			/*
			$.ajax({
				"type":"POST",
				"url":"https://graph.facebook.com",
				"data":{"batch":action.batch,"access_token":"BAACEdEose0cBAAbJXX4GvPRs4hJEYujjEHIB9smnkhTruJ5ZANsTEiwU9daZCNsx34V31BJxrz71ZCg0qZBGqal4OtXqzc1RN4nBWjxYD8hb63ZB3FH939B43MVSDhoVOWZA2R5bkpbS0vEP9tWNNMiinBCIFVqpIR0mFKrhnlg1JWuhpolQkLLi5mD8tFaorLYSpinD4ulaSqq8xTJVqZA8JboZAmMqLce2ZB9NosQzakgZDZD"},
				"success":action.success,
				"error":action.error
			});
			*/
			//console.log("RUNNN",JSON.parse(action.batch.batch));
			if(action.batch!=null) FB.api("/","POST",action.batch,action.success);
			else action.success(null);
		}else{
			_stopped=true;
			clearInterval(taskManager);
		}
	};	

	var taskManager = window.setInterval(runNext,_interval);		

	var changeInterval = function(newInterval){
		if(_interval!=newInterval){
			_interval = newInterval;
			clearInterval(taskManager);
			taskManager = window.setInterval(runNext,_interval);		
		}
	}

	var sendPriority = function(action){
		if(!_init){
			_init = true;
			_begin = new Date().getTime()/1000;
		}

		if(_stopped){
			taskManager = setInterval(runNext,_interval);
			_stopped=false;
		}
		
		_queue.unshift(action);
	}

	this.send = function(action){
		if(!_init){
			_init = true;
			_begin = new Date().getTime()/1000;
		}

		if(_stopped){
			taskManager = setInterval(runNext,_interval);
			_stopped=false;
		}
		
		_queue.push(action);

	}
}

function BaseBatchRequest(attempt,usersId,start,end){
	var _start = start;
	var _end = end;
	var _usersId = usersId;
	var _batch;
	var _weight = 1;

	this.setWeight = function(w){_weight=w;};
	this.getWeight = function(){return _weight;}

	this.makeBatch = function(start,end,add,success){

	}

	var that = this;

	this.retrySend = function(r,success,error,add){
		if(control.debug) console.log("RETRY SEND");
		if(_end-_start<=1 || attempt>=5){
			error(r);
			return null;
		}

		var start1 = _start;
		var end1 = _start + Math.floor( (_end-_start)/2 );
		var start2 = end1;
		var end2 = _end;		

		return [start1,end1,start2,end2];
	}

	this.send = function(success,error,add){
		if(control.debug) console.log("SEND ",_start,_end);

		var fba = new FacebookAction( this.makeBatch(_start,_end,add,success),this.getWeight(),function(r){
			if(control.debug) console.log(r);
			if(r==null){
				success(null,true);/*r is null when the batch was cancelled by the programmer*/
				return;
			}
			if(r.error!=undefined || r[0]==undefined || r[0].body==undefined){
				that.retrySend.apply(that,[r,success,error,add]);
				return;
			}
			var data = JSON.parse(r[0].body);

			if( data.error_code!=undefined || data.error!=undefined){
				that.retrySend.apply(that,[data,success,error,add]);
				return;
			}
			success(data);
		},function(r){ return that.retrySend.apply(that,[r,success,error,add]);});

		fc.send(fba);
	}	
}

function ThreadBatchRequest(attempt){
	BaseBatchRequest.call(this,attempt,null,null,null);

	var _super={};
	for(x in this){
		_super[x] = this[x];
	}

	this.makeBatch = function(start,end,add,success){
		var batch = [];
		var threadURL = "fql?q=Select recent_authors,message_count,thread_id from thread WHERE folder_id = 0 LIMIT 100";

		batch.push({
			"method":"GET",
			"relative_url":threadURL,				
		});

		return batch;
	}	

	this.retrySend = function(r,success,error,add){
		if(attempt>5){
			error(r);
			return;
		}

		var ibr = new ThreadBatchRequest(attempt+1);

		add(ibr);		
		success(null,true);
	}	
}

function MessageBatchRequest(attempt,author,threadId,totalMsg){
	BaseBatchRequest.call(this,attempt,null,null,null);

	var _super={};
	for(x in this){
		_super[x] = this[x];
	}

	this.makeBatch = function(start,end,add,success){
		var batch = [];

		var points = 20;
		var jump = Math.floor( totalMsg/points );
		var middle = Math.floor(jump/2);

		if( jump < 1){
			return [];
		}

		var messageURL = "fql?q=Select \"" + author + "\", message_id,created_time from message where message_id = ";
		var currentMsg = 1;

		messageURL += "\"" + threadId + "_1\" OR ";
		while( (currentMsg+middle)< (totalMsg) ){
			messageURL+= "message_id = \"" + threadId + "_" + (currentMsg + middle) + "\" OR ";
			currentMsg+=jump;
		}
		messageURL+= "message_id = \"" + threadId + "_" + totalMsg +"\"";
		if(control.debug) console.log(threadId,messageURL);

		batch.push({
			"method":"GET",
			"relative_url":messageURL,				
		});

		return batch;
	}	

	this.retrySend = function(r,success,error,add){
		if(attempt>5){
			error(r);
			return;
		}

		var ibr = new MessageBatchRequest(attempt+1,author,threadId,totalMsg);

		add(ibr);		
		success(null,true);
	}		
			
}

function MutualBatchRequest(attempt,usersId,start,end){
	BaseBatchRequest.call(this,attempt,usersId,start,end);

	var _super={};
	for(x in this){
		_super[x] = this[x];
	}

	this.makeBatch = function(start,end,add,success){
		var batch = [];
		var usersStr = "";

		for(var i=start;i<end;i++){
			usersStr+= usersId[i] + ",";
		}
		usersStr = usersStr.substring(0,usersStr.length-1);

		batch.push({
			"method":"GET",
			"relative_url":"mutualfriends?offset=0&limit=5000&ids="+usersStr
		});    	

		return batch;
	}	

	this.retrySend = function(r,success,error,add){
		var l = _super.retrySend(r,success,error,add);
		if(l==null) return;

		var mbr1 = new MutualBatchRequest(attempt+1,usersId,l[0],l[1]);
		var mbr2 = new MutualBatchRequest(attempt+1,usersId,l[2],l[3]);

		add(mbr1);		
		add(mbr2);
		success(null,true);
	}

}

function PostBatchRequest(attempt,usersId,start,end){
	BaseBatchRequest.call(this,attempt,usersId,start,end);

	var _super={};
	for(x in this){
		_super[x] = this[x];
	}

	this.makeBatch = function(start,end,add,success){
		var batch = [];
		var usersStr = "";

		for(var i=start;i<end;i++){
			usersStr+= usersId[i] + ",";
		}
		usersStr = usersStr.substring(0,usersStr.length-1);

    	batch.push({
    		"method":"GET",
    		"name":"get-post-0-" + i,
    		"relative_url":"posts?limit=500&fields=likes.limit(100).fields(id),shares,comments.limit(100).fields(from.fields(id))&ids=" + usersStr
    	});
		this.setWeight(batch.length);

	    if(control.debug) console.log(batch);

	    return batch;
	}

	this.retrySend = function(r,success,error,add){
		if(control.debug) console.log("RETRY POST");

		var l = _super.retrySend(r,success,error,add);
		if(l==null) return;

		var pbr1 = new PostBatchRequest(attempt+1,usersId,l[0],l[1]);
		var pbr2 = new PostBatchRequest(attempt+1,usersId,l[2],l[3]);

		add(pbr1);		
		add(pbr2);
		success(null,true);
	}
}

function FriendBatchRequest(attempt){
	BaseBatchRequest.call(this,attempt,null,null,null);

	this.makeBatch = function(start,end,add,success){
		var batch = [{
		    		"method":"GET",
		    		"name":"get-friends",
		    		"relative_url":"me/friends?fields=id&limit=100"
		    	}];

		return batch;
	}

	this.retrySend = function(r,success,error,add){
		if(attempt>5){
			error(r);
			return;
		}

		var fbr = new FriendBatchRequest(attempt+1);
		add(fbr);
		success(null,true);
	}
}


function TaggedBatchRequest(attempt,usersId,start,end){
	BaseBatchRequest.call(this,attempt,null,null,null);

	this.makeBatch = function(start,end,add,success){
		var batch = [{
	    		"method":"GET",
	    		"relative_url":"me/tagged?fields=from&limit=800"
	    	}
	    ];

		return batch;
	}

	this.retrySend = function(r,success,error,add){
		if(attempt>5){
			error(r);
			return;
		}

		var tbr = new TaggedBatchRequest(attempt+1);
		add(tbr);
		success(null,true);
	}	

}

function divideUserRequests(limitRequests,usersId,job,batchRequest){
	/*Some requests as Post and Mutual should be done by all the user's friends. 
	However, the Facebook API puts a limit on the number of requests inside a batch
	request (50). For those requests we need to send more than one batch request, such
	that all of them cover all the range of friends.
	*/

	var start = 0;
	var end = 0;
	while(end<usersId.length){			
		end += limitRequests;
		if(end>usersId.length) end = usersId.length;

		var br = new batchRequest(0,usersId,start,end);			
		job.addAction(br);

		start = end;
	}

	return job;
}